package thss.mse12.lau.rsa.b;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.Random;

import thss.mse12.lau.rsa.utils.RSAInterfaces;

public class RSA implements RSAInterfaces {

	private static final int KEY_LENGTH = 512;
	private static final int MAX_ENCRYPT_BLOCK = 10;

	private BigInteger privateKey;
	private BigInteger npublicKey;
	private BigInteger epublicKey;

	public RSA() {
		BigInteger int1 = BigInteger.probablePrime(KEY_LENGTH, new Random());
		BigInteger int2 = BigInteger.probablePrime(KEY_LENGTH, new Random());
		this.npublicKey = int1.multiply(int2);
		int1 = int1.add(new BigInteger("-1"));
		int2 = int2.add(new BigInteger("-1"));
		BigInteger phi = int1.multiply(int2);
		BigInteger candidateE = new BigInteger(KEY_LENGTH, new Random());
		while (true) {
			if (candidateE.gcd(phi).intValue() == 1) {
				this.epublicKey = candidateE;
				break;
			} else {
				candidateE = new BigInteger(512, new Random());
			}
		}
		this.privateKey = this.epublicKey.modInverse(phi);
	}

	@Override
	public byte[] decrypt(byte[] encryptedData) throws Exception {
		// TODO Auto-generated method stub
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] cache, block;
		int i = 0;
		int blockLength = 0;
		while (inputLen - offSet > 0) {
			blockLength = encryptedData[offSet] + encryptedData[offSet + 1];
			block = new byte[blockLength];
			for (int j = 0; j < blockLength; j++) {
				block[j] = encryptedData[offSet + j + 2];
			}
			cache = decryptSingleBlock(block);
			if(cache[0] == 0){
				out.write(cache, 1, cache.length-1);

			}else{
				out.write(cache, 0, cache.length);

			}
			offSet += blockLength + 2;
		}
		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}

	@Override
	public byte[] encrypt(byte[] data) throws Exception {
		// TODO Auto-generated method stub
		int inputLen = data.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;
		byte[] block, cache;
		int i = 0;
		while (inputLen - offSet > 0) {
			if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
				block = new byte[MAX_ENCRYPT_BLOCK + 1];
				block[0] = 0;
				for (int j = 1; j < MAX_ENCRYPT_BLOCK + 1; j++) {
					block[j] = data[offSet + j - 1];
				}
				cache = encryptSingleBlock(block);
				if (cache.length > 127) {
					out.write(127);
					out.write(cache.length - 127);
				} else {
					out.write(cache.length);
					out.write(0);
				}
			} else {
				block = new byte[inputLen - offSet + 1];
				block[0] = 0;
				for (int j = 1; j < inputLen - offSet + 1; j++) {
					block[j] = data[offSet + j - 1];
				}
				cache = encryptSingleBlock(block);
				if (cache.length > 127) {
					out.write(127);
					out.write(cache.length - 127);
				} else {
					out.write(cache.length);
				}
			}
			out.write(cache, 0, cache.length);
			i++;
			offSet = i * MAX_ENCRYPT_BLOCK;
		}
		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}

	private byte[] encryptSingleBlock(byte[] data) {
		BigInteger src = new BigInteger(data);
		BigInteger rlt = src.modPow(this.epublicKey, this.npublicKey);
		return rlt.toByteArray();
	}

	private byte[] decryptSingleBlock(byte[] data) {
		BigInteger src = new BigInteger(data);
		BigInteger rlt = src.modPow(this.privateKey, this.npublicKey);
		return rlt.toByteArray();
	}
}
